/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.instance;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Collections;
import java.util.List;

import javax.swing.Icon;

import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.comp.AbstractComponentFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.AttributeSets;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.gui.log.Loggable;
import com.cburch.logisim.tools.Pokable;
import com.cburch.logisim.images.Icons;
import com.cburch.logisim.strings.SimpleStringGetter;
import com.cburch.logisim.strings.StringGetter;
import com.cburch.logisim.collections.UnmodifiableList;

/**
 * Represents a category of components that appear in a circuit. This class
 * and <code>Component</code> share the same sort of relationship as the
 * relation between <em>classes</em> and <em>instances</em> in Java. Normally,
 * there is only one ComponentFactory created for any particular category.
 */
public abstract class InstanceFactory extends AbstractComponentFactory {
    private String name;
    private StringGetter displayName;
    private String iconName;
    private Icon icon;
    private Attribute[] attrs;
    private Object[] defaults;
    private Bounds bounds;
    private List portList;
    private Attribute facingAttribute;
    private Class pokerClass;
    private Class loggerClass;
    
    public InstanceFactory(String name) {
        this(name, new SimpleStringGetter(name));
    }
    
    public InstanceFactory(String name, StringGetter displayName) {
        this.name = name;
        this.displayName = displayName;
        this.iconName = null;
        this.icon = null;
        this.attrs = null;
        this.defaults = null;
        this.bounds = Bounds.EMPTY_BOUNDS;
        this.portList = Collections.EMPTY_LIST;
    }
    
    public final String getName() {
        return name;
    }
    
    public String getDisplayName() {
        return displayName.get();
    }
    
    public void setIconName(String value) {
        iconName = value;
        icon = null;
    }
    
    public void setIcon(Icon value) {
        iconName = "";
        icon = value;
    }
    
    public final void paintIcon(ComponentDrawContext context,
            int x, int y, AttributeSet attrs) {
        InstancePainter painter = context.getInstancePainter();
        painter.setFactory(this, attrs);
        Graphics g = painter.getGraphics();
        g.translate(x, y);
        paintIcon(painter);
        g.translate(-x, -y);
        
        if(painter.getFactory() == null) {
            Icon i = icon;
            if(i == null) {
                String n = iconName;
                if(n != null) {
                    i = Icons.getIcon(n);
                    if(i == null) n = null;
                }
            }
            if(i != null) {
                i.paintIcon(context.getDestination(), g, x, y);
            } else {
                super.paintIcon(context, x, y, attrs);
            }
        }
    }
    
    public final Component createComponent(Location loc, AttributeSet attrs) {
        InstanceComponent ret = new InstanceComponent(this, loc, attrs);
        configureNewInstance(ret.getInstance());
        return ret;
    }
    
    public void setOffsetBounds(Bounds value) {
        bounds = value;
    }

    public Bounds getOffsetBounds(AttributeSet attrs) {
        Bounds ret = bounds;
        if(ret == null) {
            throw new RuntimeException("offset bounds unknown: "
                    + "use setOffsetBounds or override getOffsetBounds");
        }
        return ret;
    }
    
    public Attribute getFacingAttribute() {
        return facingAttribute;
    }
    
    public void setFacingAttribute(Attribute value) {
        facingAttribute = value;
    }
    
    public void setAttributes(Attribute[] attrs, Object[] defaults) {
        this.attrs = attrs;
        this.defaults = defaults;
    }
    
    public AttributeSet createAttributeSet() {
        Attribute[] as = attrs;
        AttributeSet ret = as == null ? AttributeSets.EMPTY : AttributeSets.fixedSet(as, defaults);
        return ret;
    }
    
    public void setPorts(Port[] ports) {
        portList = new UnmodifiableList(ports);
    }
    
    public void setPorts(List ports) {
        portList = Collections.unmodifiableList(ports);
    }
    
    public List getPorts() {
        return portList;
    }
    
    public void setInstancePoker(Class pokerClass) {
        if(isClassOk(pokerClass, InstancePoker.class)) {
            this.pokerClass = pokerClass;
        }
    }
    
    public void setInstanceLogger(Class loggerClass) {
        if(isClassOk(loggerClass, InstanceLogger.class)) {
            this.loggerClass = loggerClass;
        }
    }
    
    public boolean isClassOk(Class sub, Class sup) {
        boolean isSub = sup.isAssignableFrom(sub);
        if(!isSub) {
            System.err.println(sub.getName() + " must be a subclass of " + sup.getName()); //OK
            return false;
        }
        try {
            sub.getConstructor(new Class[0]);
            return true;
        } catch (SecurityException e) {
            System.err.println(sub.getName() + " needs its no-args constructor to be public"); //OK
        } catch (NoSuchMethodException e) {
            System.err.println(sub.getName() + " is missing a no-arguments constructor"); //OK
        }
        return true;
    }
    
    public final Object getFeature(Object key, AttributeSet attrs) {
        if(key == FACING_ATTRIBUTE_KEY) return facingAttribute;
        return super.getFeature(key, attrs);
    }
    
    public final void drawGhost(ComponentDrawContext context, Color color,
            int x, int y, AttributeSet attrs) {
        InstancePainter painter = context.getInstancePainter();
        Graphics g = painter.getGraphics();
        g.setColor(color);
        g.translate(x, y);
        painter.setFactory(this, attrs);
        paintGhost(painter);
        g.translate(-x, -y);
        if(painter.getFactory() == null) {
            super.drawGhost(context, color, x, y, attrs);
        }
    }
    
    public void paintIcon(InstancePainter painter) {
        painter.setFactory(null, null);
    }
    
    public void paintGhost(InstancePainter painter) {
        painter.setFactory(null, null);
    }
    
    public abstract void paintInstance(InstancePainter painter);
    public abstract void propagate(InstanceState state);
    public void destroyComponent(Component comp)
    {
    	InstanceComponent i = new InstanceComponent(this, comp.getLocation(), comp.getAttributeSet());
        destroyInstance(i.getInstance());        
    }
    
    // event methods
    protected void configureNewInstance(Instance instance) { }
    protected void destroyInstance(Instance instance) { }
    protected void instanceAttributeChanged(Instance instance, Attribute attr) { }
    protected Object getInstanceFeature(Instance instance, Object key) {
        if(key == Pokable.class && pokerClass != null) {
            return new InstancePokerAdapter(instance.getComponent(), pokerClass);
        } else if(key == Loggable.class && loggerClass != null) {
            return new InstanceLoggerAdapter(instance.getComponent(), loggerClass);
        } else {
            return null;
        }
    }    
}
